/**
 * @file lv_draw_sdl_texture_cache.c
 *
 */

/*********************
 *      INCLUDES
 *********************/

#include "../../lv_conf_internal.h"

#if LV_USE_GPU_SDL

#include "lv_draw_sdl_texture_cache.h"

#include "lv_draw_sdl_utils.h"

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

typedef struct
{
    SDL_Texture* texture;
    void* userdata;
    lv_lru_free_t* userdata_free;
    lv_draw_sdl_cache_flag_t flags;
} draw_cache_value_t;

typedef struct
{
    lv_sdl_cache_key_magic_t magic;
} temp_texture_key_t;

typedef struct
{
    lv_coord_t width, height;
} temp_texture_userdata_t;

static void draw_cache_free_value(draw_cache_value_t*);

static draw_cache_value_t* draw_cache_get_entry(lv_draw_sdl_ctx_t* ctx,
        const void* key, size_t key_length,
        bool* found);
/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_draw_sdl_texture_cache_init(lv_draw_sdl_ctx_t* ctx)
{
    ctx->internals->texture_cache = lv_lru_create(LV_GPU_SDL_LRU_SIZE, 65536,
                                    (lv_lru_free_t*) draw_cache_free_value, NULL);
}

void lv_draw_sdl_texture_cache_deinit(lv_draw_sdl_ctx_t* ctx)
{
    lv_lru_del(ctx->internals->texture_cache);
}

SDL_Texture* lv_draw_sdl_texture_cache_get(lv_draw_sdl_ctx_t* ctx,
        const void* key, size_t key_length, bool* found)
{
    return lv_draw_sdl_texture_cache_get_with_userdata(ctx, key, key_length, found,
            NULL);
}

SDL_Texture* lv_draw_sdl_texture_cache_get_with_userdata(lv_draw_sdl_ctx_t* ctx,
        const void* key, size_t key_length,
        bool* found, void** userdata)
{
    draw_cache_value_t* value = draw_cache_get_entry(ctx, key, key_length, found);

    if (!value)
    {
        return NULL;
    }

    if (userdata)
    {
        *userdata = value->userdata;
    }

    return value->texture;
}

bool lv_draw_sdl_texture_cache_put(lv_draw_sdl_ctx_t* ctx, const void* key,
                                   size_t key_length, SDL_Texture* texture)
{
    return lv_draw_sdl_texture_cache_put_advanced(ctx, key, key_length, texture,
            NULL, NULL, 0);
}

bool lv_draw_sdl_texture_cache_put_advanced(lv_draw_sdl_ctx_t* ctx,
        const void* key, size_t key_length,
        SDL_Texture* texture, void* userdata, void userdata_free(void*),
        lv_draw_sdl_cache_flag_t flags)
{
    lv_lru_t* lru = ctx->internals->texture_cache;
    draw_cache_value_t* value = SDL_malloc(sizeof(draw_cache_value_t));
    value->texture = texture;
    value->userdata = userdata;
    value->userdata_free = userdata_free;
    value->flags = flags;

    if (!texture)
    {
        return lv_lru_set(lru, key, key_length, value, 1) == LV_LRU_OK;
    }

    if (flags & LV_DRAW_SDL_CACHE_FLAG_MANAGED)
    {
        /* Managed texture doesn't count into cache size */
        LV_LOG_INFO("cache texture %p", texture);
        return lv_lru_set(lru, key, key_length, value, 1) == LV_LRU_OK;
    }

    Uint32 format;
    int access, width, height;

    if (SDL_QueryTexture(texture, &format, &access, &width, &height) != 0)
    {
        return false;
    }

    LV_LOG_INFO("cache texture %p, %d*%d@%dbpp", texture, width, height,
                SDL_BITSPERPIXEL(format));
    return lv_lru_set(lru, key, key_length, value,
                      width * height * SDL_BITSPERPIXEL(format) / 8) == LV_LRU_OK;
}

lv_draw_sdl_cache_key_head_img_t* lv_draw_sdl_texture_img_key_create(
    const void* src, int32_t frame_id, size_t* size)
{
    lv_draw_sdl_cache_key_head_img_t header;
    /* VERY IMPORTANT! Padding between members is uninitialized, so we have to wipe them manually */
    SDL_memset(&header, 0, sizeof(header));
    header.magic = LV_GPU_CACHE_KEY_MAGIC_IMG;
    header.type = lv_img_src_get_type(src);
    header.frame_id = frame_id;
    void* key;
    size_t key_size;

    if (header.type == LV_IMG_SRC_FILE || header.type == LV_IMG_SRC_SYMBOL)
    {
        size_t srclen = SDL_strlen(src);
        key_size = sizeof(header) + srclen;
        key = SDL_malloc(key_size);
        SDL_memcpy(key, &header, sizeof(header));
        /*Copy string content as key value*/
        SDL_memcpy(key + sizeof(header), src, srclen);
    }
    else
    {
        key_size = sizeof(header) + sizeof(void*);
        key = SDL_malloc(key_size);
        SDL_memcpy(key, &header, sizeof(header));
        /*Copy address number as key value*/
        SDL_memcpy(key + sizeof(header), &src, sizeof(void*));
    }

    *size = key_size;
    return (lv_draw_sdl_cache_key_head_img_t*) key;
}

static void draw_cache_free_value(draw_cache_value_t* value)
{
    if (value->texture && !(value->flags & LV_DRAW_SDL_CACHE_FLAG_MANAGED))
    {
        LV_LOG_INFO("destroy texture %p", value->texture);
        SDL_DestroyTexture(value->texture);
    }

    if (value->userdata_free)
    {
        value->userdata_free(value->userdata);
    }

    SDL_free(value);
}

static draw_cache_value_t* draw_cache_get_entry(lv_draw_sdl_ctx_t* ctx,
        const void* key, size_t key_length,
        bool* found)
{
    lv_lru_t* lru = ctx->internals->texture_cache;
    draw_cache_value_t* value = NULL;
    lv_lru_get(lru, key, key_length, (void**) &value);

    if (!value)
    {
        if (found)
        {
            *found = false;
        }

        return NULL;
    }

    if (found)
    {
        *found = true;
    }

    return value;
}

#endif /*LV_USE_GPU_SDL*/

